/**
 * Moves an element in an array from one position to another.
 * @param {T[]} array
 * @param {number} from
 * @param {number} to
 */
import {warn} from './log'

export function moveArrayElement<T>(array: T[], from: number, to: number): T[] {
    if (to >= 0 && to < array.length) {
        array.splice(to, 0, array.splice(from, 1)[0])
    }
    return array
}

// export function moveArray<T>(array: T[], from: number, to: number): T[] {
//     if (to >= 0 && to < array.length) {
//         // console.log(array, from, to)
//         const clonedArray = moveArrayElement(array, from, to - 1);
//     }
//     return array;
// }


export function swapList<T>(arr: T[], index1: number, index2: number): T[] {
    arr[index1] = arr.splice(index2, 1, arr[index1])[0]
    return arr
}

/**
 * Convert a hyphen-delimited string to camelCase.
 * @param {string} str
 * @returns {string}
 */
export function camelize(str: string) {
    return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}

/**
 * Convert an object's keys from hyphen-delimited to camelCase.
 * @param {Record<string, any>} object
 * @returns {Record<string, any>}
 */
export function objectMap(object: Record<any, any>) {
    return Object.keys(object).reduce((result, key) => {
        if (typeof object[key] !== 'undefined') {
            result[camelize(key)] = object[key]
        }
        return result
    }, {} as Record<string, any>)
}

/**
 * Removes an element from an array.
 * @param {T[]} array
 * @param {number} index
 * @returns {T[]}
 */
export function removeElement<T>(array: T[], index: number) {
    if (Array.isArray(array)) return array.splice(index, 1)
}

/**
 * Inserts an element into an array.
 * @param {T[]} array
 * @param {number} index
 * @param element
 */
export function insertElement<T>(array: T[], index: number, element: any) {
    if (Array.isArray(array)) return array.splice(index, 0, element)
}

/**
 * If the value is undefined, return true, otherwise return false.
 * @param {any} value - any
 * @returns {value is undefined}
 */
export function isUndefined(value: any): value is undefined {
    return typeof value === 'undefined'
}

/**
 * If the value is string, return true, otherwise return false.
 * @param value
 * @returns {value is string}
 */
export function isString(value: any): value is string {
    return typeof value === 'string'
}

/**
 * Inserts a element into the DOM at a given index.
 * @param parentElement
 * @param element
 * @param {number} index
 */
export function insertNodeAt(
    parentElement: Element,
    element: Element,
    index: number
) {
    const refElement = parentElement.children[index]
    parentElement.insertBefore(element, refElement)
}

/**
 * Removes a node from the DOM.
 * @param {Node} node
 */
export function removeNode(node: Node) {
    if (node.parentNode) node.parentNode.removeChild(node)
}

/**
 * Get an element by selector.
 * @param {string} selector
 * @param parentElement
 * @returns {Element}
 */
export function getElementBySelector(
    selector: string,
    parentElement: Document | Element = document
) {
    let el: HTMLElement | null = null
    if (typeof parentElement?.querySelector === 'function') {
        el = parentElement?.querySelector?.(selector)
    } else {
        el = document.querySelector(selector)
    }
    if (!el) {
        warn(`Element not found: ${selector}`)
    }
    return el as HTMLElement
}

/**
 * It takes a function and returns a function that executes the original function and then executes the second function.
 * @param {Function} fn - The function to be executed
 * @param {Function} afterFn - The function to be executed after the original function.
 * @param {any} [ctx=null] - The context of the function.
 * @returns {Function}
 */
export function mergeExecuted<T extends (...args: []) => any>(
    fn: T,
    afterFn: T,
    ctx: any = null
) {
    return function (...args: any[]) {
        fn.apply(ctx, args)
        return afterFn.apply(ctx, args)
    }
}

/**
 * Merge the options and events.
 * @param {Record<string, any>} options
 * @param {Record<string, any>} events
 * @returns {Record<string, any>}
 */
export function mergeOptionsEvents(
    options: Record<string, any>,
    events: Record<string, any>
) {
    const evts = {...options}
    Object.keys(events).forEach(key => {
        if (evts[key]) {
            evts[key] = mergeExecuted(options[key], events[key])
        } else {
            evts[key] = events[key]
        }
    })
    return evts
}

export function isHTMLElement(el: any): el is HTMLElement {
    return el instanceof HTMLElement
}

/**
 * @param obj
 * @param fn
 */
export function forEachObject<T extends Record<string, any>>(
    obj: T,
    fn: (key: keyof T, value: T[keyof T]) => void
) {
    Object.keys(obj).forEach(key => {
        fn(key, obj[key])
    })
}

function matches(el: Element, selector: string) {
    if (!selector) return

    selector[0] === '>' && (selector = selector.substring(1))

    if (el) {
        try {
            if (el.matches) {
                return el.matches(selector)
            } else if ((el as unknown as any).msMatchesSelector) {
                return (el as unknown as any).msMatchesSelector(selector)
            } else if ((el as unknown as any).webkitMatchesSelector) {
                return (el as unknown as any).webkitMatchesSelector(selector)
            }
        } catch (_) {
            return false
        }
    }

    return false
}

/**
 * Returns the index of an element within its parent for a selected set of
 * elements
 * @param  {Element} el
 * @param  {selector} selector
 * @return {number}
 */
export function index(el: Element | null, selector?: any) {
    let index = 0

    if (!el || !el.parentNode) {
        return -1
    }

    /* jshint boss:true */
    while ((el = el.previousElementSibling)) {
        if (
            el.nodeName.toUpperCase() !== 'TEMPLATE' &&
            (!selector || matches(el, selector))
        ) {
            index++
        }
    }

    return index
}

export function swapNodes(n1: Element, n2: Element) {
    const p1 = n1.parentNode,
        p2 = n2.parentNode
    let i1, i2

    if (!p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1)) return

    // eslint-disable-next-line prefer-const
    i1 = index(n1)
    i2 = index(n2)

    if (p1.isEqualNode(p2) && i1 < i2) {
        i2++
    }
    p1.insertBefore(n2, p1.children[i1])
    p2.insertBefore(n1, p2.children[i2])
}

export function needInsertDom(options: any, dom: Element, args: {
    swapDoms: { odd: HTMLElement[], even: HTMLElement[] },
    maxDom: HTMLElement
}) {
    const {swapDoms, maxDom} = args
    // console.log('dom', dom, options)
    // const parent = dom.parentNode;
    const utils = options.value.utils;

    const prevPos = utils.getPosition(dom)
    const bPos = prevPos[prevPos.length / 2];
    if (prevPos && prevPos.length > 0) {
        let i = bPos[0],
            j = bPos[1] > 0 ? bPos[1] - 1 : 0;

        while (i >= 0) {
            while (j >= 0) {
                const curDom = utils.getElementByPos(dom, [i, j])
                const curPos = utils.getPosition(curDom)
                // 同行招到小组件, 就插入到小组件内部
                if (curPos && curPos.length === 1) {
                    return curDom.nextElementSibling
                }
                j--;
            }
            i--;
            j = utils.getRootVars.iconCol - 1;
            // 走到这个逻辑获取下一个节点信息会获取到插入过后的DOM节点而不是前一个,
            const curDom = utils.getElementByPos(dom, [i, j]);
            const transformDoms = [...swapDoms.odd, ...swapDoms.even, maxDom]
            const transformIds = transformDoms.map(v => v.dataset.id);
            // 如果存在这种恰巧的情况需要返回的不是找到元素的下一个元素, 而是交换的最大元素dom, 并且需要排除当前交换的下一个节点不属于当前容器的最后一个节点
            if (!curDom.nextElementSibling) {
                return null;
            }
            if (transformIds.includes(curDom.nextElementSibling.getAttribute('data-id'))) {
                return maxDom
            }
            // console.log('curDom 换行a吧', prevPos, curDom, nextElementSibling)
            return curDom.nextElementSibling
        }
    }
}


